﻿using System.Collections.Generic;
using MathNet.Numerics.Distributions;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.MachineLearning.GradEAAlg
{
	public class GradEAElements
	{
		#region - Population creation. -
		public static List<AbstractIndividual> CreatePopulation (EAParameters parameters)
		{
			var res = new List<AbstractIndividual>();

			for (int i = 0; i < parameters.PopulationSize; i++)
			{
				var ind = new Individual();
				ind.Init(parameters.IndividualSize, parameters.RNG, parameters.GeneValueRange, parameters.MinGeneValue);
				res.Add(ind);
			}

			return res;
		}
		#endregion

		#region - Changes search. -
		public static ChangeData PickChanges(ChangesLog cLog, Operator op, Individual ind, DistanceMeasure1D measure, float eps)
		{
			// get operator relevant changes.
			var opChanges = cLog.SearchByOperator(op);
			if (opChanges.Count == 0) return null;

			// find the closest changes in the eps-neighborhood of the given individual.
			var clChanges = FindClosestChanges(opChanges, ind, measure, eps);
			if (clChanges.Count == 0) return null;

			// get the most useful change using information from the [FitnessComparator].
			return FindTheBestChange(clChanges, FitnessComparator.MinimizeFitness);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns changes, which are made in the points closest to the given individual with respect to the neighborhood size and distance measure.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="ind"></param>
		/// <param name="measure"></param>
		/// <param name="eps"></param>
		/// <returns></returns>
		public static List<ChangeData> FindClosestChanges(List<ChangeData> data, Individual ind, DistanceMeasure1D measure, float eps)
		{
			var res = new List<ChangeData>();
			var genes = ind.Genes.ToArray();

			foreach (var changeData in data)
			{
				var tempGenes = ((Individual)changeData.Individual).Genes.ToArray();
				var dist = measure(genes, tempGenes);
				if (dist < eps)
				{
					res.Add(changeData);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns the best change from the given set with respect to whether minimization of maximization of the fitness function is used.
		/// </summary>
		/// <param name="data"></param>
		/// <param name="minimizationProblem"></param>
		/// <returns></returns>
		public static ChangeData FindTheBestChange(List<ChangeData> data, bool minimizationProblem)
		{
			var tempDict = new Dictionary<ChangeData, float>();
			foreach (var changeData in data)
			{
				tempDict.Add(changeData, VectorMath.Sum(changeData.GradientEstimate));
			}

			// sort changes by increase of sum of gradient estimates.
			tempDict = StructMath.SortByValue(tempDict);

			if (!minimizationProblem)
			{	// reverse sorting order.
				tempDict = StructMath.Reverse(tempDict);
			}

			var keys = new ChangeData[tempDict.Keys.Count];
			tempDict.Keys.CopyTo(keys, 0);

			var res = (ChangeData)keys[0].Clone();
			if ((minimizationProblem && res.GradientEstimate[0] > 0)
				|| (!minimizationProblem && res.GradientEstimate[0] < 0))
			{
				// if all changes around current individual are unsuccessfull, then create random change.
				var sigma = res.Actor.Parameters.MutationStep;
				var distr = new NormalDistribution(0, sigma);
				res.Change = VectorMath.CreateRandomVector(distr, res.Individual.Size);
			}
			return res;

			//// FitnessComparator.MinimizeFitness == false
			//return keys[0].GradientEstimate[0] > 0 ? keys[0] : null;
		} 
		#endregion
	}
}